package btree;

import java.io.IOException;
import java.util.ArrayList;

import diskmgr.*;
import global.*;

public class BTreeFile
{

	private static BTHeaderPage header;
	private PageId headerID;
	private String fileName;

	public static BTHeaderPage getHeaderPage()
	{
		return header;
	}

	public BTreeFile(String fileName) throws FileEntryNotFoundException
	{
		try
		{
			PageId pageno = SystemDefs.JavabaseDB.get_file_entry(fileName);
			if (pageno == null)
			{
				throw new FileEntryNotFoundException(null,
						"btree.BTreeFile(String fileName): file entry not found");
			} else
			{
				this.fileName = fileName;
				headerID = pageno;
				header = new BTHeaderPage(headerID);
			}
		} catch (Exception e)
		{
			throw new FileEntryNotFoundException(e,
					"btree.BTreeFile(String fileName): file entry not found");
		}
	}

	public BTreeFile(String filename, int keytype, int keysize,
			int delete_fashion)
	{
		fileName = filename;

		try
		{
			PageId pageno = SystemDefs.JavabaseDB.get_file_entry(filename);
			if (pageno == null)
			{
				try
				{
					header = new BTHeaderPage();
					headerID = header.getCurPage();
					// add the new file entry
					SystemDefs.JavabaseDB.add_file_entry(filename, headerID);
					header.set_keyType((short) keytype);
					header.set_deleteFashion(delete_fashion);
					header.set_maxKeySize(keysize);
					// create the first leaf page
					BTLeafPage firstLeaf = new BTLeafPage(keytype);
					firstLeaf.setNextPage(new PageId(-1));
					header.set_rootId(firstLeaf.getCurPage());
				} catch (Exception e)
				{

					e.printStackTrace();
				}
			} else
			{
				headerID = pageno;
				header = new BTHeaderPage(headerID);
			}
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public void close()
	{
		try
		{
			SystemDefs.JavabaseBM.unpinPage(headerID, true);
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public BTFileScan new_scan(KeyClass lo_key, KeyClass hi_key)
	{
		BTFileScan scan = new BTFileScan();
		scan.setLow_key(lo_key);
		scan.setHigh_key(hi_key);
		scan.setCurrent();
		return scan;
	}

	public void freeingPages(BTSortedPage page)
	{
		try
		{
			if (page.getType() == NodeType.LEAF)
				SystemDefs.JavabaseBM.freePage(page.getCurPage());
			else
			{
				RID rid = new RID();
				KeyDataEntry kde = ((BTIndexPage) page).getFirst(rid);
				BTSortedPage toLoop;

				while (kde != null)
				{
					PageId pid = ((IndexData) (kde.data)).getData();

					toLoop = new BTSortedPage(pid, BTreeFile.getHeaderPage()
							.get_keyType());
					toLoop.dumpPage();
					freeingPages(toLoop);
					kde = ((BTIndexPage) page).getNext(rid);
					SystemDefs.JavabaseBM.freePage(toLoop.getCurPage());

				}
			}
		} catch (Exception e)
		{
			e.printStackTrace();
		}

	}

	public void destroyFile()
	{
		try
		{
			PageId firstPage = header.get_rootId();
			BTSortedPage page = new BTSortedPage(firstPage, BTreeFile
					.getHeaderPage().get_keyType());
			page.dumpPage();
			if (page.getType() != NodeType.LEAF)
			{
				freeingPages(page);
			} else
			{
				SystemDefs.JavabaseBM.freePage(firstPage);
			}
			SystemDefs.JavabaseBM.unpinPage(headerID, true);
			SystemDefs.JavabaseDB.delete_file_entry(fileName);
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public boolean Delete(KeyClass key, RID rid) throws DeleteRecException
	{
		KeyDataEntry kde = new KeyDataEntry(key, rid);
		BTLeafPage leafPage;
		try
		{
			leafPage = search(key);
		} catch (BTreeFileException e1)
		{
			throw new DeleteRecException(e1,
					"BtreeFile.Delete(): faild to search for leaf page");
		}
		try
		{
			boolean bool = leafPage.delEntry(kde);
			SystemDefs.JavabaseBM.unpinPage(leafPage.getCurPage(), true);
			return bool;
		} catch (Exception e)
		{
			throw new DeleteRecException(e, "BtreeFile.Delete(): delete failed");
		}
	}

	public BTLeafPage search(KeyClass key) throws BTreeFileException
	{
		BTSortedPage root;
		try
		{
			// get the root page of the tree
			root = new BTSortedPage(header.get_rootId(), header.get_keyType());
			SystemDefs.JavabaseBM.unpinPage(header.get_rootId(), true);
		} catch (Exception e)
		{
			throw new BTreeFileException(
					"btree.BTreeFile.tree_search(): failed to get the root page");
		}
		return tree_search(root, key);

	}

	private BTLeafPage tree_search(BTSortedPage currentPage, KeyClass key)
			throws BTreeFileException
	{
		boolean isLeafPage = false;
		try
		{
			isLeafPage = (currentPage.getType() == NodeType.LEAF);
		} catch (Exception e)
		{
			throw new BTreeFileException(
					"btree.BTreeFile.tree_search(): couldn't get Page Type");
		}

		// Check if Current Page is a leaf page

		if (isLeafPage) // it's a leaf page
		{
			BTLeafPage requiredPage;
			try
			{
				requiredPage = new BTLeafPage(currentPage.getCurPage(),
						header.get_keyType());
			} catch (Exception e)
			{
				throw new BTreeFileException(
						"btree.BTreeFile.tree_search(): couldn't pin the required page");
			}

			return requiredPage;
		}

		else
		// it's an index page
		{
			BTIndexPage currentIndexPage = null;
			try
			{
				currentIndexPage = new BTIndexPage(currentPage.getCurPage(),
						header.get_keyType());
			} catch (Exception e)
			{
				throw new BTreeFileException(
						"btree.BTreeFile.tree_search(): couldn't pin the Index page");
			}
			RID currentRecordId = new RID();
			KeyDataEntry firstKey;

			// get the first Key from the index page
			try
			{
				firstKey = currentIndexPage.getFirst(currentRecordId);
			} catch (Exception e)
			{
				throw new BTreeFileException(
						"btree.BTreeFile.tree_search(): couldn't get first keyDataEntry");
			}

			// compare the given key to the first key in the page
			int comparison = 0;
			try
			{
				comparison = BT.keyCompare(key, firstKey.key);
			} catch (Exception e)
			{
				throw new BTreeFileException(
						"btree.BTreeFile.tree_search(): key comparison missmatch");
			}
			BTSortedPage nextLevelPage;
			if (comparison < 0) // the given key is less than the first key in
								// the index page
			{
				try
				{
					// make a new BTSortedPage Object
					nextLevelPage = new BTSortedPage(
							currentIndexPage.getLeftLink(),
							header.get_keyType());
					SystemDefs.JavabaseBM.unpinPage(
							currentIndexPage.getLeftLink(), true);
				} catch (Exception e)
				{
					throw new BTreeFileException(
							"btree.BTreeFile.tree_search(): couldn't pin/unpin next level page");
				}
				// recursive call to the next level of index pages
				return tree_search(nextLevelPage, key);
			} else
			// the given key is not less than the first key in the index page
			{
				KeyDataEntry key1;
				KeyDataEntry key2;

				try
				{
					key1 = currentIndexPage.getNext(currentRecordId);
					key2 = currentIndexPage.getNext(currentRecordId);
					while (key2 != null && (BT.keyCompare(key, key2.key) < 0))
					{
						key1 = key2;
						key2 = currentIndexPage.getNext(currentRecordId);
					}
				} catch (Exception e)
				{
					throw new BTreeFileException(
							"btree.BTreeFile.tree_search(): error finding the next level page");
				}
				try
				{
					// make a new BTSortedPage Object
					nextLevelPage = new BTSortedPage(
							((IndexData) key1.data).getData(),
							header.get_keyType());
					SystemDefs.JavabaseBM.unpinPage(
							((IndexData) key1.data).getData(), true);
				} catch (Exception e)
				{
					throw new BTreeFileException(
							"btree.BTreeFile.tree_search(): couldn't pin/unpin next level page");
				}
				// recursive call to the next level of index pages
				return tree_search(nextLevelPage, key);
			}
		}
	}

	public void insert(KeyClass key, RID rid) throws Exception
	{
		BTSortedPage root;
		try
		{
			root = new BTSortedPage(header.get_rootId(), header.get_keyType());
			SystemDefs.JavabaseBM.unpinPage(root.getCurPage(), true);
		} catch (Exception e)
		{
			throw new BTreeFileException(
					"btree.BTreeFile.tree_insert(): couldn't pin/unpin root page");
		}
		tree_insert(root, key, rid, null);
	}

	private void tree_insert(BTSortedPage root, KeyClass key, RID rid,
			KeyDataEntry newChildEntry) throws Exception
	{
		// check for root type
		boolean isIndexPage = false;

		try
		{
			isIndexPage = root.getType() == NodeType.INDEX;
		} catch (IOException e)
		{
			throw new BTreeFileException(
					"btree.BTreeFile.tree_insert(): failed to find the page type");
		}

		if (isIndexPage) // it's an index page
		{
			BTIndexPage currentIndexPage;

			try
			{
				currentIndexPage = new BTIndexPage(root.getCurPage(),
						header.get_keyType());
				SystemDefs.JavabaseBM.unpinPage(root.getCurPage(), true);
			} catch (Exception e)
			{
				throw new BTreeFileException(
						"btree.BTreeFile.tree_insert(): couldn't dump the Index page");
			}

			// find the appropriate next-level page

			RID currentRecordId = new RID();
			BTSortedPage nextLevelPage;
			KeyDataEntry key1;
			KeyDataEntry key2;

			try
			{
				key1 = currentIndexPage.getNext(currentRecordId);
				key2 = currentIndexPage.getNext(currentRecordId);
				while (key2 != null && (BT.keyCompare(key, key2.key) < 0))
				{
					key1 = key2;
					key2 = currentIndexPage.getNext(currentRecordId);
				}
			} catch (Exception e)
			{
				throw new BTreeFileException(
						"btree.BTreeFile.tree_search(): error finding the next level page");
			}
			try
			{
				// make a new BTSortedPage Object
				nextLevelPage = new BTSortedPage(
						((IndexData) key1.data).getData(), header.get_keyType());

			} catch (Exception e)
			{
				throw new BTreeFileException(
						"btree.BTreeFile.tree_insert(): failed to pin/unpin next level page");
			}

			// recursive call to the insert method with the next-level page
			tree_insert(nextLevelPage, key, rid, newChildEntry);

			SystemDefs.JavabaseBM.unpinPage(nextLevelPage.getCurPage(), true);

			if (newChildEntry == null)
			{
				return;
			} else
			{
				// check if there is available space for the new key entry
				boolean freeSpaceAvaliable = false;

				try
				{
					freeSpaceAvaliable = currentIndexPage.available_space() >= header
							.get_maxKeySize();
				} catch (Exception e)
				{
					throw new BTreeFileException(
							"btree.BTreeFile.tree_search(): failed to check for available space");
				}

				if (freeSpaceAvaliable) // Index Page has available space
				{
					try
					{
						// insert the new key to the Index page
						currentIndexPage.insertRecord(newChildEntry);

					} catch (Exception e)
					{
						throw new BTreeFileException(
								"btree.BTreeFile.tree_insert(): failed to insert the new key entry to the next level IndexPage");
					}

					newChildEntry = null;
					return;
				} else
				// Index page has no available space
				{
					BTIndexPage newIndexPage;
					try
					{
						newIndexPage = new BTIndexPage(header.get_keyType());
						newIndexPage.setNextPage(new PageId(-1));
						newIndexPage.setPrevPage(new PageId(-1));
					} catch (Exception e)
					{
						throw new BTreeFileException(
								"btree.BTreeFile.tree_insert(): failed to create a new index page");
					}

					// Split Current Index page
					int numOfRecords = currentIndexPage.getSlotCnt();
					int i = 1;
					RID tempRid = new RID();
					KeyDataEntry currentKey;
					boolean isPut = false;
					while (i <= (numOfRecords / 2))
					{
						try
						{
							currentKey = currentIndexPage.getNext(tempRid);

							if (BT.keyCompare(key, currentKey.key) < 0
									&& !isPut)
							{
								isPut = true;
							}
						} catch (Exception e)
						{
							throw new BTreeFileException(
									"btree.BTreeFile.tree_insert(): failed to split index page");
						}
						i++;
					}

					while (i <= numOfRecords)
					{
						try
						{
							currentKey = currentIndexPage.getNext(tempRid);
							newIndexPage.insertRecord(currentKey);
							currentIndexPage.deleteRecord(tempRid);
							i++;
						} catch (Exception e)
						{
							throw new BTreeFileException(
									"btree.BTreeFile.tree_insert(): failed to split index page");
						}
					}

					// put newChildEntry in the right place

					if (BT.keyCompare(newChildEntry.key,
							newIndexPage.getFirst(tempRid).key) < 0)
					{
						currentIndexPage.insertRecord(newChildEntry);
					} else
					{
						newIndexPage.insertRecord(newChildEntry);
					}

					// find the smallest key in the newIndexPage
					KeyDataEntry smallestKey = newIndexPage.getFirst(tempRid);

					// set the newChildEntry
					newChildEntry.key = smallestKey.key;
					newChildEntry.data = new IndexData(
							newIndexPage.getCurPage());

					// set the left link of the new IndexPage
					newIndexPage.setLeftLink(((IndexData) smallestKey.data)
							.getData());
					newIndexPage.deleteRecord(tempRid);

					// check if the current page is the root page

					if (currentIndexPage.getCurPage().pid == header
							.get_rootId().pid)
					{
						BTIndexPage newRoot = new BTIndexPage(
								header.get_keyType());
						newRoot.setLeftLink(currentIndexPage.getCurPage());
						newRoot.insertRecord(newChildEntry);
						header.set_rootId(newRoot.getCurPage());
						SystemDefs.JavabaseBM.unpinPage(newRoot.getCurPage(),
								true);
					}

					// unpin Pinned Pages

					try
					{
						SystemDefs.JavabaseBM.unpinPage(
								currentIndexPage.getCurPage(), true);
						SystemDefs.JavabaseBM.unpinPage(
								newIndexPage.getCurPage(), true);
					}

					catch (Exception e)
					{
						throw new BTreeFileException(
								"btree.BTreeFile.tree_insert(): failed to unpin pinned pages");
					}

				}
			}

		}

		// it's a leaf page

		else
		{
			BTLeafPage currentLeafPage;

			try
			{
				currentLeafPage = new BTLeafPage(root.getCurPage(),
						header.get_keyType());
				SystemDefs.JavabaseBM.unpinPage(root.getCurPage(), true);
			} catch (Exception e)
			{
				throw new BTreeFileException(
						"btree.BTreeFile.tree_insert(): couldn't dump the Index page");
			}

			boolean freeSpaceAvaliable;

			try
			{
				freeSpaceAvaliable = currentLeafPage.available_space() >= header
						.get_maxKeySize();
			} catch (Exception e)
			{
				throw new BTreeFileException(
						"btree.BTreeFile.tree_search(): failed to check for available space");
			}

			if (freeSpaceAvaliable) // Index Page has available space
			{
				try
				{
					// insert the new entry to the leaf page
					currentLeafPage.insertRecord(new KeyDataEntry(key, rid));
					// unpin the leaf page
					SystemDefs.JavabaseBM.unpinPage(
							currentLeafPage.getCurPage(), true);

				} catch (Exception e)
				{
					throw new BTreeFileException(
							"btree.BTreeFile.tree_insert(): failed to insert the new key entry to the next level IndexPage");
				}

				newChildEntry = null;
				return;
			}

			else
			// free space not available
			{
				BTLeafPage newLeafPage;
				try
				{
					newLeafPage = new BTLeafPage(header.get_keyType());
					newLeafPage.setNextPage(new PageId(-1));
					newLeafPage.setPrevPage(currentLeafPage.getCurPage());
				} catch (Exception e)
				{
					throw new BTreeFileException(
							"btree.BTreeFile.tree_insert(): failed to create a new Leaf page");
				}
				int numOfRecords = currentLeafPage.getSlotCnt();
				int i = 1;
				RID tempRid = new RID();
				KeyDataEntry currentKey;

				while (i <= (numOfRecords / 2))
				{
					try
					{
						currentKey = currentLeafPage.getNext(tempRid);
					} catch (Exception e)
					{
						throw new BTreeFileException(
								"btree.BTreeFile.tree_insert(): failed to split Leaf page");
					}
					i++;
				}

				while (i <= numOfRecords)
				{
					try
					{
						currentKey = currentLeafPage.getNext(tempRid);
						newLeafPage.insertRecord(currentKey);
						currentLeafPage.deleteRecord(tempRid);
						i++;
					} catch (Exception e)
					{
						throw new BTreeFileException(
								"btree.BTreeFile.tree_insert(): failed to split Leaf page");
					}
				}

				// put newChildEntry in the right place

				if (BT.keyCompare(newChildEntry.key,
						newLeafPage.getFirst(tempRid).key) < 0)
				{
					currentLeafPage.insertRecord(new KeyDataEntry(key, rid));
				} else
				{
					newLeafPage.insertRecord(new KeyDataEntry(key, rid));
				}

				// find the smallest key in the newLeafPage
				KeyDataEntry smallestKey = newLeafPage.getFirst(tempRid);

				// set the newChildEntry
				newChildEntry.key = smallestKey.key;
				newChildEntry.data = new IndexData(newLeafPage.getCurPage());

				// unpin Pinned Pages

				try
				{
					SystemDefs.JavabaseBM.unpinPage(
							currentLeafPage.getCurPage(), true);
					SystemDefs.JavabaseBM.unpinPage(newLeafPage.getCurPage(),
							true);
				} catch (Exception e)
				{
					throw new BTreeFileException(
							"btree.BTreeFile.tree_insert(): failed to unpin pinned pages");
				}
			}
		}

	}

}
